package com.quectel.core.module.forum.service.impl.mongo;

import cn.hutool.core.lang.func.LambdaUtil;
import com.quectel.constant.core.forum.ForumConstants;
import com.quectel.constant.global.SystemConstants;
import com.quectel.util.aspect.lock.annotation.Lock;
import com.quectel.core.config.TransactionManagerConfig;
import com.quectel.core.constants.MongoCollectionConstants;
import com.quectel.core.constants.RedisCacheConstants;
import com.quectel.core.constants.RedisLockConstants;
import com.quectel.core.module.MongoAuthComponent;
import com.quectel.core.module.forum.dto.ForumTopicDto;
import com.quectel.core.module.forum.dto.mongo.*;
import com.quectel.core.module.forum.entity.mongo.ForumPostModel;
import com.quectel.core.module.forum.service.ForumTopicService;
import com.quectel.core.module.forum.service.mongo.*;
import com.quectel.core.module.mobileuser.dto.MobileUserDto;
import com.quectel.core.module.mobileuser.service.MobileUserService;
import com.quectel.util.common.CopyUtils;
import com.quectel.util.common.DateUtils;
import com.quectel.util.common.Request;
import com.quectel.util.kit.CacheKit;
import com.quectel.util.kit.Snowflake;
import com.quectel.util.mongo.MongodbUtils;
import com.quectel.util.spring.SpringUtils;
import lombok.extern.slf4j.Slf4j;
import org.ansj.splitWord.analysis.IndexAnalysis;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.dubbo.config.annotation.DubboService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.TextCriteria;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.ReflectionUtils;

import java.lang.reflect.Field;
import java.util.*;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * @author zhangheng
 * @email nolan.zhang@quectel.com
 * @date 2023/5/8 15:52
 * @description 帖子
 */
@DubboService
@Slf4j
public class ForumPostServiceImpl implements ForumPostService {

    @Autowired
    private MongoAuthComponent mongoAuthComponent;

    @Autowired
    private ForumMsgService forumMsgService;
    @Autowired
    private ForumTopicCountService forumTopicCountService;

    @Autowired
    private ForumLikeService forumLikeService;

    @Autowired
    private ForumCommentService forumCommentService;
    @Autowired
    private ForumShareService forumShareService;
    @Autowired
    private ForumTopicService forumTopicService;
    @Autowired
    private MobileUserService mobileUserService;
    @Autowired
    private ForumPersonCountService forumPersonCountService;
    @Autowired
    private ForumAuditRecordService forumAuditRecordService;

    /**
     * 入参解释:实体id
     */
    private static final Function<Long, String> FORUM_POST_CACHE_BY_ID_FUNC = id -> String.format(RedisCacheConstants.PROJECT_NAME + "FORUM_POST_CACHE_BY_ID_FUNC:%d", id);

    /**
     * 清除缓存方法
     */
    private static final Consumer<Long> CLEAR_CACHE_FUNC = id -> {
        CacheKit.invalidRedisCache(FORUM_POST_CACHE_BY_ID_FUNC.apply(id));
    };


    private Criteria sqlWhere(Map<String, Object> params) {
        Request request = Request.configParams(params);

        Criteria authByAllQuery = mongoAuthComponent.buildTenantConstraintsQuery(params
                , LambdaUtil.getFieldName(ForumPostModel::getTenantId)
        );
        Criteria criteria = new Criteria();

        Long tenantId = request.getLong(LambdaUtil.getFieldName(ForumPostModel::getTenantId));
        if (tenantId != null) {
            criteria.and(LambdaUtil.getFieldName(ForumPostModel::getTenantId)).is(tenantId);
        }
        Long id = request.getLong(LambdaUtil.getFieldName(ForumPostModel::getId));
        if (id != null) {
            criteria.and(SystemConstants.MONGO_ENTITY_ID_FLAG).is(id);
        }

        String cityCode = request.getString(LambdaUtil.getFieldName(ForumPostModel::getCityCode));
        if (StringUtils.isNotBlank(cityCode)) {
            criteria.and(LambdaUtil.getFieldName(ForumPostModel::getCityCode)).is(cityCode);
        }

        Byte visibleFlag = request.getByte(LambdaUtil.getFieldName(ForumPostModel::getVisibleFlag));
        if (visibleFlag != null) {
            criteria.and(LambdaUtil.getFieldName(ForumPostModel::getVisibleFlag)).is(visibleFlag);
        }

        Long villageId = request.getLong(LambdaUtil.getFieldName(ForumPostModel::getVillageId));
        if (villageId != null) {
            criteria.and(LambdaUtil.getFieldName(ForumPostModel::getVillageId)).is(villageId);
        }

        Long authorId = request.getLong(LambdaUtil.getFieldName(ForumPostModel::getAuthorId));
        if (authorId != null) {
            criteria.and(LambdaUtil.getFieldName(ForumPostModel::getAuthorId)).is(authorId);
        }

        Byte type = request.getByte(LambdaUtil.getFieldName(ForumPostModel::getType));
        if (type != null) {
            criteria.and(LambdaUtil.getFieldName(ForumPostModel::getType)).is(type);
        }

        Object[] topics = request.getArray(ForumConstants.TOPICS, Object.class);
        if (topics != null && topics.length > 0) {
            criteria.and(LambdaUtil.getFieldName(ForumPostModel::getTopics)).in(topics);
        }

        Byte deleteFlag = request.getByte(LambdaUtil.getFieldName(ForumPostModel::getDeletedFlag));
        if (deleteFlag != null) {
            criteria.and(LambdaUtil.getFieldName(ForumPostModel::getDeletedFlag)).is(deleteFlag);
        }

        Byte topFlag = request.getByte(LambdaUtil.getFieldName(ForumPostModel::getTopFlag));
        if (topFlag != null) {
            criteria.and(LambdaUtil.getFieldName(ForumPostModel::getTopFlag)).is(topFlag);
        }

        Criteria publishTimeCriteria = new Criteria(LambdaUtil.getFieldName(ForumPostModel::getPublishTime));
        String startTime = request.getString(ForumConstants.PUBLISH_TIME_SLOT_KEY_PAIR.getLeft());
        if (StringUtils.isNotBlank(startTime)) {
            publishTimeCriteria.gte(DateUtils.parse(startTime, DateUtils.FormatType.COMMON));
        }
        String endTime = request.getString(ForumConstants.PUBLISH_TIME_SLOT_KEY_PAIR.getRight());
        if (StringUtils.isNotBlank(endTime)) {
            publishTimeCriteria.lte(DateUtils.parse(endTime, DateUtils.FormatType.COMMON));
        }
        if (StringUtils.isNotBlank(startTime) || StringUtils.isNotBlank(endTime)) {
            criteria.andOperator(publishTimeCriteria);
        }

        return new Criteria().andOperator(authByAllQuery, criteria);
    }

    private ForumPostDto paddingField(ForumPostDto dto) {
        if (dto != null) {
            MobileUserDto mobileUserDto = mobileUserService.selectCacheById(dto.getAuthorId());
            if (mobileUserDto != null) {
                dto.setNickName(mobileUserDto.getNickname());
                dto.setHeadImg(mobileUserDto.getHeadImg());
            }

            if (CollectionUtils.isNotEmpty(dto.getTopics())) {
                List<String> topicsName = new ArrayList<>();
                dto.getTopics().forEach(
                        topicId -> {
                            ForumTopicDto forumTopicDto = forumTopicService.selectCacheById(topicId);
                            if (forumTopicDto != null) {
                                topicsName.add(forumTopicDto.getName());
                            }
                        }
                );
                dto.setTopicNames(topicsName);
            }
        }
        return dto;
    }

    @Override
    public Byte isAuthor(Long authorId, Long userId) {
        if (authorId == null || userId == null) {
            return SystemConstants.NO;
        }
        if (authorId.equals(userId)) {
            return SystemConstants.YES;
        } else {
            return SystemConstants.NO;
        }
    }

    @Override
    public Byte ifLike(Long postId, Long userId) {
        if (postId == null || userId == null) {
            return SystemConstants.NO;
        }
        //是否点过赞
        ForumLikeDto forumLikeDto = forumLikeService.selectByPostAndOperator(postId, userId);
        if (forumLikeDto != null) {
            return SystemConstants.YES;
        } else {
            return SystemConstants.NO;
        }
    }


    @Override
    @Lock(
            lockExpresses = {
                    RedisLockConstants.FORUM_FORUM_LOCK + "#{#forumPostId}"
            }
    )
    @Transactional(transactionManager = TransactionManagerConfig.MONGO_TRANSACTION_MANAGER, rollbackFor = RuntimeException.class)
    public void like(Long forumPostId, Long operatorId, Byte status) {

        ForumPostDto dto = selectById(forumPostId);
        //如果帖子不存在或者被删除
        if (dto == null || SystemConstants.YES.equals(dto.getDeletedFlag())) {
            return;
        }

        Integer likeCount = dto.getLikeCount();

        if (SystemConstants.YES.equals(status)) {
            dto.setLikeCount(likeCount + 1);

            ForumLikeDto forumLikeDto = new ForumLikeDto();
            forumLikeDto.setForumPostId(forumPostId);
            forumLikeDto.setPersonId(operatorId);
            forumLikeDto.setLikeTime(new Date());
            forumLikeService.save(forumLikeDto);
            //更新后操作
            forumMsgService.operateSuccessNotify(dto.getTenantId(), dto.getId(), operatorId, dto.getAuthorId(), ForumConstants.ForumHandleType.LIKE.getType());


        } else if (SystemConstants.NO.equals(status)) {
            dto.setLikeCount(likeCount - 1);

            forumLikeService.deleteByPostAndOperator(forumPostId, operatorId);
            //更新点赞数量
            forumPersonCountService.updateCommentCount(dto.getTenantId(), operatorId, ForumConstants.ForumHandleType.LIKE.getType(), -1);

            //删除对应的消息
            Map<String, Object> params = new HashMap<>(4);
            params.put(LambdaUtil.getFieldName(ForumMsgDto::getMobileUserId), operatorId);
            params.put(LambdaUtil.getFieldName(ForumMsgDto::getOperatedPersonId), operatorId);
            params.put(LambdaUtil.getFieldName(ForumMsgDto::getBusinessId), dto.getId());
            params.put(LambdaUtil.getFieldName(ForumMsgDto::getForumMsgType), ForumConstants.ForumHandleType.LIKE.getType());
            forumMsgService.deleteOneByParams(params);

        }
        //更新
        updateById(dto);

    }

    @Override
    @Lock(
            lockExpresses = {
                    RedisLockConstants.FORUM_FORUM_LOCK + "#{#forumPostId}"
            }
    )
    @Transactional(transactionManager = TransactionManagerConfig.MONGO_TRANSACTION_MANAGER, rollbackFor = RuntimeException.class)
    public void comment(Long forumPostId, Long operatorId, Long householdId, String comment) {

        ForumPostDto dto = selectById(forumPostId);
        //如果帖子不存在或者被删除
        if (dto == null || SystemConstants.YES.equals(dto.getDeletedFlag())) {
            return;
        }
        //更新评论数量
        dto.setCommentCount(dto.getCommentCount() + 1);
        updateById(dto);
        //新增评论
        ForumCommentDto forumCommentDto = new ForumCommentDto();
        forumCommentDto.setForumPostId(forumPostId);
        forumCommentDto.setPersonId(operatorId);
        forumCommentDto.setHouseholdId(householdId);
        forumCommentDto.setCommentedPersonId(dto.getAuthorId());
        forumCommentDto.setContent(comment);
        forumCommentDto.setCommentTime(new Date());
        forumCommentDto.setId(forumCommentService.save(forumCommentDto));

        forumMsgService.operateSuccessNotify(dto.getTenantId(), forumCommentDto.getId(), operatorId, dto.getAuthorId(), ForumConstants.ForumHandleType.POST_COMMENT.getType());


    }

    @Override
    @Lock(
            lockExpresses = {
                    RedisLockConstants.FORUM_FORUM_LOCK + "#{#forumPostId}"
            }
    )
    @Transactional(transactionManager = TransactionManagerConfig.MONGO_TRANSACTION_MANAGER, rollbackFor = RuntimeException.class)
    public void share(Long forumPostId, Long operatorId) {
        ForumPostDto dto = selectById(forumPostId);
        //如果帖子不存在或者被删除
        if (dto == null || SystemConstants.YES.equals(dto.getDeletedFlag())) {
            return;
        }
        //更新评论数量
        dto.setShareCount(dto.getShareCount() + 1);
        updateById(dto);

        ForumLikeDto forumLikeDto = new ForumLikeDto();
        forumLikeDto.setForumPostId(forumPostId);
        forumLikeDto.setPersonId(operatorId);
        forumLikeDto.setLikeTime(new Date());

        forumLikeService.save(forumLikeDto);

        forumMsgService.operateSuccessNotify(dto.getTenantId(), dto.getId(), operatorId, dto.getAuthorId(), ForumConstants.ForumHandleType.SHARE.getType());

    }


    @Override
    @Lock(
            lockExpresses = {
                    RedisLockConstants.FORUM_FORUM_LOCK + "#{#forumPostId}"
            }
    )
    public void updateForumCount(Long forumPostId, Byte handleType, Integer offset) {
        ForumPostDto dto = selectById(forumPostId);
        if (dto == null) {
            return;
        }

        ForumConstants.ForumHandleType byType = ForumConstants.ForumHandleType.getByType(handleType);
        if (byType == null) {
            return;
        }

        Integer count = 0;
        try {
            Field field = ReflectionUtils.findField(ForumPostDto.class, byType.getFieldName() + "Count");
            if (field != null) {
                field.setAccessible(true);
                count = (Integer) field.get(dto);
                count += offset;
                ReflectionUtils.setField(field, dto, count <= 0 ? 0 : count);
                ForumPostService forumPostService = SpringUtils.getBean(ForumPostService.class);
                forumPostService.updateById(dto);
            }

        } catch (IllegalAccessException e) {
            log.error(e.getMessage(), e);
        }
    }


    @Override
    @Transactional(transactionManager = TransactionManagerConfig.MONGO_TRANSACTION_MANAGER, rollbackFor = RuntimeException.class)
    public Long post(ForumPostDto dto) {

        ForumPostService forumPostService = SpringUtils.getBean(ForumPostService.class);
        dto.setId(forumPostService.save(dto));
        forumPostService.postSuccessNotify(dto);

        return dto.getId();
    }

    @Override
    public void postSuccessNotify(ForumPostDto dto) {
        //成功操作
        forumMsgService.operateSuccessNotify(dto.getTenantId(), dto.getId(), dto.getAuthorId(), dto.getAuthorId(), ForumConstants.ForumHandleType.POST.getType());

        //更新话题数量
        if (CollectionUtils.isNotEmpty(dto.getTopics())) {
            dto.getTopics().forEach(aLong -> forumTopicCountService.updateTopicCount(aLong, ForumConstants.ForumHandleType.POST.getType(), 1));
        }
    }


    @Override
    public ForumPostDto selectCacheById(Long id) {
        return CacheKit.cacheToRedis(
                () -> selectById(id),
                FORUM_POST_CACHE_BY_ID_FUNC.apply(id),
                SystemConstants.NOT_NULL_CACHE_EXPIRE_SECONDS
        );
    }

    @Override
    public ForumPostDto selectById(Long id) {
        ForumPostModel one = MongodbUtils.findOne(id, MongoCollectionConstants.FORUM_POST, ForumPostModel.class);
        return paddingField(CopyUtils.copyObj(one, ForumPostDto.class));
    }


    @Override
    public List<ForumPostDto> queryList(Map<String, Object> params) {
        Request request = Request.configParams(params);
        Criteria criteria = sqlWhere(params);
        Query query = new Query();
        query.addCriteria(criteria);

        String content = request.getString(LambdaUtil.getFieldName(ForumPostModel::getContent));
        if (StringUtils.isNotBlank(content)) {
            String stringWithOutNature = IndexAnalysis.parse(content).toStringWithOutNature(" ");
            TextCriteria textCriteria = TextCriteria.forDefaultLanguage().matchingAny(stringWithOutNature);
            query.addCriteria(textCriteria);
        }

        return
                MongodbUtils.page(
                        query,
                        request.getPageNo() - 1,
                        request.getPageSize(),
                        Sort.by(Sort.Order.desc(LambdaUtil.getFieldName(ForumPostModel::getTopFlag)), Sort.Order.desc(SystemConstants.ENTITY_ID_FLAG)),
                        ForumPostModel.class,
                        MongoCollectionConstants.FORUM_POST
                ).stream().map(model -> {
                    ForumPostDto dto = paddingField(CopyUtils.copyObj(model, ForumPostDto.class));

                    //是否是作者
                    Long currentUser = request.getLong(ForumConstants.CURRENT_USER);
                    if (currentUser != null) {
                        dto.setAuthorFlag(isAuthor(dto.getAuthorId(), currentUser));
                        //是否点过赞
                        dto.setLikeFlag(ifLike(dto.getId(), currentUser));
                    }

                    return dto;
                }).collect(Collectors.toList());
    }

    @Override
    public int queryTotal(Map<String, Object> params) {
        return (int) MongodbUtils.count(new Query(sqlWhere(params)), MongoCollectionConstants.FORUM_POST);
    }

    @Override
    public Long save(ForumPostDto dto) {
        ForumPostModel entity = CopyUtils.copyObj(dto, ForumPostModel.class);
        entity.setId(Snowflake.nextId());

        if (StringUtils.isNotBlank(entity.getContent())) {
            //去除特殊符号
            String filteredText = entity.getContent().replaceAll("[^\u4e00-\u9fa5a-zA-Z0-9]", "");
            entity.setContentText(IndexAnalysis.parse(filteredText).toStringWithOutNature(" "));
        }
        MongodbUtils.insert(entity, MongoCollectionConstants.FORUM_POST);
        return entity.getId();
    }

    @Override
    @Lock(
            lockExpresses = {
                    RedisLockConstants.FORUM_FORUM_LOCK + "#{#dto.id}"
            }
    )
    public void updateById(ForumPostDto dto) {
        Update update = new Update()
                .set(LambdaUtil.getFieldName(ForumPostModel::getContent), dto.getContent())
                .set(LambdaUtil.getFieldName(ForumPostModel::getContentText), IndexAnalysis.parse(dto.getContent()).toStringWithOutNature(" "))
                .set(LambdaUtil.getFieldName(ForumPostModel::getImageUrls), dto.getImageUrls())
                .set(LambdaUtil.getFieldName(ForumPostModel::getLikeCount), dto.getLikeCount())
                .set(LambdaUtil.getFieldName(ForumPostModel::getCommentCount), dto.getCommentCount())
                .set(LambdaUtil.getFieldName(ForumPostModel::getShareCount), dto.getShareCount())
                .set(LambdaUtil.getFieldName(ForumPostModel::getVisibleFlag), dto.getVisibleFlag())
                .set(LambdaUtil.getFieldName(ForumPostModel::getDeletedFlag), dto.getDeletedFlag())
                .set(LambdaUtil.getFieldName(ForumPostModel::getEditTime), dto.getEditTime());

        if (StringUtils.isNotBlank(dto.getContent())) {
            if (StringUtils.isNotBlank(dto.getContent())) {
                //去除特殊符号
                String filteredText = dto.getContent().replaceAll("[^\u4e00-\u9fa5a-zA-Z0-9]", "");
                update.set(LambdaUtil.getFieldName(ForumPostModel::getContentText), IndexAnalysis.parse(filteredText).toStringWithOutNature(" "));
            }
        }

        MongodbUtils.update(
                new Query(
                        Criteria.where(SystemConstants.MONGO_ENTITY_ID_FLAG).is(dto.getId())
                ),
                update
                ,
                MongoCollectionConstants.FORUM_POST

        );
        CLEAR_CACHE_FUNC.accept(dto.getId());
    }

    @Override
    @Lock(
            lockExpresses = {
                    RedisLockConstants.FORUM_FORUM_LOCK + "#{#id}"
            }
    )
    @Transactional(transactionManager = TransactionManagerConfig.MONGO_TRANSACTION_MANAGER, rollbackFor = RuntimeException.class)
    public void logicDeleteById(Long id) {
        ForumPostDto dto = selectById(id);
        if (dto == null) {
            return;
        }
        dto.setDeletedFlag(SystemConstants.YES);
        ForumPostService forumPostService = SpringUtils.getBean(ForumPostService.class);
        forumPostService.updateById(dto);

        CLEAR_CACHE_FUNC.accept(id);

        if (CollectionUtils.isNotEmpty(dto.getTopics())) {
            dto.getTopics().forEach(
                    topicId -> forumTopicCountService.updateTopicCount(topicId, ForumConstants.ForumHandleType.POST.getType(), -1)
            );
        }

        //删除所有的评论
        forumCommentService.deleteByPostId(id);

        CLEAR_CACHE_FUNC.accept(id);

    }

    @Override
    @Transactional(transactionManager = TransactionManagerConfig.MONGO_TRANSACTION_MANAGER, rollbackFor = RuntimeException.class)
    public void auditDeleteBatch(Long tenantId, Long auditUserId, List<Byte> violationTypes, String remark, Long[] ids) {
        ForumPostService forumPostService = SpringUtils.getBean(ForumPostService.class);
        for (Long id : ids) {
            ForumPostDto forumPostDto = forumPostService.selectById(id);
            if (forumPostDto == null || SystemConstants.YES.equals(forumPostDto.getDeletedFlag())) {
                continue;
            }

            forumPostService.logicDeleteById(id);

            //新增审核记录
            ForumAuditRecordDto forumAuditRecordDto = new ForumAuditRecordDto();
            forumAuditRecordDto.setTenantId(tenantId);
            forumAuditRecordDto.setMobileUserId(forumPostDto.getAuthorId());
            forumAuditRecordDto.setSourceType(ForumConstants.ForumAuditObjectEnum.POST.getType());
            forumAuditRecordDto.setSourceId(id);
            forumAuditRecordDto.setAuditUserId(auditUserId);
            forumAuditRecordDto.setResult(ForumConstants.ForumAuditResultEnum.DELETE_POST.getResult());
            forumAuditRecordDto.setViolationTypes(violationTypes);
            forumAuditRecordDto.setRemark(remark);
            forumAuditRecordDto.setAuditTime(new Date());

            forumAuditRecordService.save(forumAuditRecordDto);

        }
    }

    @Override
    public void logicDeleteBatch(Long[] ids) {
        ForumPostService forumPostService = SpringUtils.getBean(ForumPostService.class);
        for (Long id : ids) {
            forumPostService.logicDeleteById(id);
        }
    }


    @Override
    public void deleteById(Long id) {
        MongodbUtils.delete(id, MongoCollectionConstants.FORUM_POST);
        CLEAR_CACHE_FUNC.accept(id);
    }

    @Override
    public void deleteBatch(Long[] ids) {
        for (Long id : ids) {
            MongodbUtils.delete(id, MongoCollectionConstants.FORUM_POST);
        }
    }

}
